import { Tree } from "antd";
import React, { MouseEventHandler, useEffect, useState } from "react";
import styles from "@/styles/TreeBar.module.sass";
import { BasicDataNode } from "rc-tree";
import TwoFileRefactoringDetector, {
  NodePairWithRefactorings,
} from "@/types/TwoFileRefactoringDetector";
import {
  DeleteOutlined,
  FileAddOutlined,
  RightOutlined,
  LeftOutlined,
  DownOutlined,
} from "@ant-design/icons";
import {
  NodeDataType,
  NodeType,
  RefactorTypeForClassification,
} from "@/types/TreeBarTypes";

export interface TreeItemData extends BasicDataNode {
  title: string | React.ReactNode;
  data: NodeDataType;
  key: React.Key;
  icon?: React.ReactNode;
  switcherIcon?: React.ReactNode;
  children?: TreeItemData[];
}

export type PathAndDetector = {
  path: string;
  detector?: TwoFileRefactoringDetector;
};

export default function TreeBar(props: {
  selectedKeys: string[];
  pathAndDetector: PathAndDetector[];
  isBase: boolean;
  position: string;
  selectHandler: (
    data: NodeDataType | undefined,
    key: React.Key | undefined
  ) => void;
}) {
  const onSelect = (
    selectedKeys: React.Key[],
    info: { selectedNodes: TreeItemData[] }
  ) => {
    if (!selectedKeys.length) {
      // cancal selecting a selected key
      props.selectHandler(undefined, undefined);
      return;
    }
    const selectedNode = info.selectedNodes[0];
    // actually only allow one selection at one time, so only need to get index 0
    props.selectHandler(selectedNode.data, selectedNode.key);
  };

  const onExpand = (expandedKeysValue: React.Key[]) => {
    setKeys(expandedKeysValue);
  };

  // states
  const [treeData, setTreeData] = useState<TreeItemData[]>([]);
  const [keys, setKeys] = useState<React.Key[]>([]);

  // get the tree data needed from props
  useEffect(() => {
    const pathAndDetectors2TreeData: (
      pathAndDetectors: PathAndDetector[]
    ) => TreeItemData[] = (pathAndDetectors) => {
      let treeDataArr: TreeItemData[] = [
        {
          title: "",
          data: {
            pathString: "",
            nodeType: NodeType.TreeNode,
            isBase: props.isBase,
          },
          key: "",
        },
      ]; // tmp node to construct data
      let father: TreeItemData = treeDataArr[0];

      pathAndDetectors.forEach((pathAndDetector: PathAndDetector) => {
        // for each pair withDetetor
        const eachPath = pathAndDetector.path;
        const pathSplitted = eachPath.split("/"); // for different os splitters
        pathSplitted.map((segment, i) => {
          // 对地址的每一段
          if (!father.children) {
            father.children = [];
          }
          if (i !== pathSplitted.length - 1) {
            // 不是最后一段  找匹配
            let index = father.children
              .map((child) => child.title)
              .indexOf(segment);

            if (index === -1) {
              // unmatch
              const len = father.children.push({
                data: {
                  pathString:
                    father.key === "" ? segment : father.key + "/" + segment,
                  nodeType: NodeType.TreeNode,
                  isBase: props.isBase,
                },
                key: father.key === "" ? segment : father.key + "/" + segment,
                title: segment,
                selectable: false,
              });
              index = len - 1;
            }
            father = father.children[index]; // refresh for the next segment
          } else {
            // 最后一段（push进文件名）
            father.children.push({
              data: {
                pathString: father.key + "/" + segment, // a/b/c/d.java
                nodeType: NodeType.FileNode,
                isBase: props.isBase,
              },
              key: father.key + "/" + segment,
              title: segment,
              children: [],
            });
            father = father.children[father.children.length - 1]; //更新father指向 .java
            if (!pathAndDetector.detector) {
              // no detector for this path(added or deleted)
              father.title = (
                <span className={props.isBase ? "deletedColor" : "addedColor"}>
                  {father.title}
                </span>
              );
              father.icon = props.isBase ? (
                <DeleteOutlined className="deletedColor" />
              ) : (
                <FileAddOutlined className="addedColor" />
              );
            } else {
              // 有detector
              father.icon = <span className="iconfont icon-java" />;
              // 定义函数塞入不同重构类型
              const getRefactoringData: (
                nodePairWithRefactorings: NodePairWithRefactorings[],
                fatherData: NodeDataType,
                refactorTypeForClassification: RefactorTypeForClassification
              ) => TreeItemData = (
                nodePairWithRefactorings,
                fatherData,
                refactorTypeForClassification
              ) => {
                // for each kind of refactorings
                return {
                  isBase: props.isBase,
                  title: refactorTypeForClassification,
                  icon: <span className="iconfont icon-faxian" />,
                  key:
                    fatherData.pathString + ">" + refactorTypeForClassification, // a/b/c/d.java>MethodRefactorings
                  data: {
                    isBase: props.isBase,
                    pathString: fatherData.pathString,
                    nodeType: NodeType.RefactorTypeNode,
                  },
                  refactorType: refactorTypeForClassification,
                  selectable: false,
                  children: nodePairWithRefactorings.map((r) => ({
                    // for each refactorings********************************
                    key:
                      fatherData.pathString +
                      ">" +
                      refactorTypeForClassification +
                      ">" +
                      JSON.stringify(r.nodes1) +
                      JSON.stringify(r.nodes2),
                    isBase: props.isBase,
                    icon:
                      r.nodes1.length > 0 ? (
                        r.nodes2.length > 0 ? (
                          <span className="iconfont icon-16gl-M modifiedColor" />
                        ) : (
                          <span className="iconfont icon-16gl-D deletedColor" />
                        )
                      ) : (
                        <span className="iconfont icon-16gl-A addedColor" />
                      ),
                    title:
                      (r.nodes1.map((n) => n.displayName).toString() || "") +
                      (r.nodes1.map((n) => n.displayName).toString() &&
                        r.nodes2.map((n) => n.displayName).toString() &&
                        "->") +
                      (r.nodes2.map((n) => n.displayName).toString() || ""),
                    data: {
                      isBase: props.isBase,
                      pathString: fatherData.pathString,
                      nodeType: NodeType.RefactorNode,
                      refactorType: r.refactoringTypes,
                      refactorTypeForClassification:
                        refactorTypeForClassification,
                      baseRanges: r.nodes1.map((rNd) => rNd.range),
                      modifiedRanges: r.nodes2.map((rNd) => rNd.range),
                    },
                  })),
                };
              };

              if (pathAndDetector.detector.methodRefactorings.length > 0)
                father.children?.push(
                  getRefactoringData(
                    pathAndDetector.detector.methodRefactorings,
                    father.data,
                    RefactorTypeForClassification.MethodRefactorings
                  )
                );

              if (pathAndDetector.detector.variableRefactorings.length > 0)
                father.children?.push(
                  getRefactoringData(
                    pathAndDetector.detector.variableRefactorings,
                    father.data,
                    RefactorTypeForClassification.FieldValueRefactorings
                  )
                );

              if (pathAndDetector.detector.constructorRefactorings.length > 0)
                father.children?.push(
                  getRefactoringData(
                    pathAndDetector.detector.constructorRefactorings,
                    father.data,
                    RefactorTypeForClassification.ConstructorRefactorings
                  )
                );

              if (
                pathAndDetector.detector.typeDeclarationRefactorings.length > 0
              )
                father.children?.push(
                  getRefactoringData(
                    pathAndDetector.detector.typeDeclarationRefactorings,
                    father.data,
                    RefactorTypeForClassification.TypeDeclarationRefactorings
                  )
                );
            }
            father = treeDataArr[0]; //  reset for the next path string(point to root)
          }
        });
      });
      if (!!treeDataArr[0].children)
        return treeDataArr[0].children as TreeItemData[]; // flattern root
      else return [];
    };
    setTreeData(pathAndDetectors2TreeData(props.pathAndDetector));
  }, [props.isBase, props.pathAndDetector]);

  // open all the nodes in the tree asynchronously
  useEffect(() => {
    const getKeysUnder: (node: TreeItemData) => React.Key[] = (node) => {
      const arr = [node.key];
      if (node.children) {
        node.children.forEach((child) => {
          arr.push(...getKeysUnder(child));
        });
      }
      return arr;
    };
    const list: React.Key[] = [];
    treeData.forEach((node) => {
      list.push(...getKeysUnder(node));
    });
    setKeys(list);
  }, [treeData]);

  // 返y一个处理拖拽控制宽度事件函数
  const dragMouseDownGenerator = (position: string) => {
    if (position === "left" || position === "right") {
      const dragMouseDown: MouseEventHandler<HTMLDivElement> = (de) => {
        de.preventDefault();
        if (treeBarRef.current) {
          const initX = de.clientX;
          const initWidth = treeBarRef.current?.offsetWidth;
          // 定义ducument.mousemove
          const documentMouseMove = (e: MouseEvent) => {
            e.preventDefault();
            let distance = e.clientX - initX; // get horizontal move distance
            if (treeBarRef.current) {
              if (position === "left") distance *= -1;
              treeBarRef.current.style.width = `${initWidth + distance}px`;
            }
          };
          // 定义document.mousedown
          const documentMouseDown = () => {
            document.addEventListener("mousemove", documentMouseMove);
          };
          // 定义document.mouseup
          const documentMouseUp = () => {
            // clean up
            document.removeEventListener("mousemove", documentMouseMove);
            document.removeEventListener("mousedown", documentMouseDown);
            document.removeEventListener("mouseup", documentMouseUp);
          };
          document.addEventListener("mousedown", documentMouseDown);
          document.addEventListener("mouseup", documentMouseUp);
        }
      };
      return dragMouseDown;
    } else {
      console.error("typo in ", position);
      return undefined;
    }
  };

  const treeBarRef = React.createRef<HTMLDivElement>();
  return (
    <>
      {props.position === "right" && (
        <div className={styles.leftDragger}>
          <div
            className={styles.triangle}
            onMouseDown={dragMouseDownGenerator("left")}
          >
            <LeftOutlined />
          </div>
        </div>
      )}
      <aside ref={treeBarRef} className={styles.treeBar}>
        <div className={styles.treeTitle}>
          <h3>{props.isBase ? "初始文件树" : "变更后文件树"}</h3>
        </div>
        <div className={styles.treeWrapper}>
          <Tree
            selectedKeys={props.selectedKeys}
            showLine={{ showLeafIcon: false }}
            showIcon={true}
            switcherIcon={<DownOutlined />}
            onSelect={onSelect}
            treeData={treeData}
            expandedKeys={keys}
            onExpand={onExpand}
          />
        </div>
      </aside>
      {props.position === "left" && (
        <div className={styles.rightDragger}>
          <div
            className={styles.triangle}
            onMouseDown={dragMouseDownGenerator("right")}
          >
            <RightOutlined />
          </div>
        </div>
      )}
    </>
  );
}
